import gtk
import pango


def SetFont(widget, font):
    if font is not None:
        f = pango.FontDescription(font)
        widget.modify_font(f)
    return widget

def KeymapMixinMeta(name, bases, env):
    __gsignals__ = {
        "key_press_event" : "override",
        #"key_release_event" : "override",
    }
    base = bases[0]
    def do_key_press_event(self, evt):
        if self.owner:
            if self.owner.do_key_press_event(evt):
                return True
        if base.do_key_press_event(self, evt):
            return True
        return False
    #def do_key_release_event(self, evt):
    #    if base.do_key_release_event(self, evt):
    #        return True
    #    if self.owner:
    #        return self.owner.do_key_release_event(evt)
    #    return False
    env["owner"] = None
    env["__gsignals__"]  = __gsignals__
    env["do_key_press_event"] = do_key_press_event
    #env["do_key_release_event"] = do_key_release_event
    meta = type(base)
    return meta(name, bases, env)


def _layout_paned(pane, children):
    if len(children) == 1:
        return children[0]
    p = pane()
    p.pack1(children[0], resize = True)
    p.pack2(_layout_paned(pane, children[1:]), resize = True)
    return p

def _layout(box, pane, children):
    obj = box()
    if not children:
        return obj
    obj.pack_start(_layout_paned(pane, children))
    return obj

def HBox(*children):
    return _layout(gtk.HBox, gtk.HPaned, children)

def VBox(*children):
    return _layout(gtk.VBox, gtk.VPaned, children)

def HFixed(child):
    obj = gtk.HBox()
    obj.pack_start(child, expand = False, fill = False)
    return obj

def VFixed(child):
    obj = gtk.VBox()
    obj.pack_start(child, expand = False, fill = False)
    return obj

def Fixed(child):
    return VFixed(HFixed(child))

def MenuItem(name, action, keybinding = None, accel_group = None):
    obj = gtk.MenuItem(name)
    if action is not None:
        obj.connect("activate", action)
    if keybinding:
        key, mod = gtk.accelerator_parse(keybinding)
        if key == 0:
            raise ValueError("invalid key string: %r" % (keybinding,))
        obj.add_accelerator("activate", accel_group, key, mod, gtk.ACCEL_VISIBLE)
    return obj

def MenuSep():
    return gtk.SeparatorMenuItem()

def Menu(name, *children):
    obj = gtk.Menu()
    for child in children:
        obj.append(child)
    obj2 = gtk.MenuItem(name)
    obj2.set_submenu(obj)
    return obj2

def MenuBar(*menus):
    obj = gtk.MenuBar()
    for mitem in menus:
        obj.append(mitem)
    return VFixed(obj)

class _Window(gtk.Window):
    __metaclass__ = KeymapMixinMeta

def Window(title, child, owner = None):
    obj = _Window(gtk.WINDOW_TOPLEVEL)
    obj.owner = owner
    obj.set_title(title)
    obj.set_resizable(True)  
    obj.add(child)
    return obj

def Frame(title, child):
    obj = gtk.Frame(title)
    obj.add(child)
    obj.show()
    return obj

def Label(text):
    if isinstance(text, basestring):
        obj = gtk.Label(text)
    else:
        obj = gtk.Label(text.get())
        text.register(lambda v: obj.set_text(v.get()))
    return obj

class _Entry(gtk.Entry):
    __metaclass__ = KeymapMixinMeta

def TextLine(svalue, readonly = False, owner = None):
    obj = _Entry()
    obj.owner = owner
    obj.set_editable(not readonly)
    def user_change(obj):
        svalue.set(obj.get_text())
    obj.connect("changed", user_change)
    svalue.register(obj.set_text)
    return obj

class _TextView(gtk.TextView):
    __metaclass__ = KeymapMixinMeta

def TextBox(svalue, font = None):
    buf = gtk.TextBuffer()
    obj = SetFont(_TextView(buf), font)
    svalue.register(lambda v: buf.set_text(v.get()))
    return Scrolled(obj)

class _ScrolledWindow(gtk.ScrolledWindow):
    __metaclass__ = KeymapMixinMeta

def Scrolled(child, owner = None):
    obj = _ScrolledWindow()
    obj.owner = owner
    obj.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
    obj.add(child)
    return obj

class _TreeView(gtk.TreeView):
    __metaclass__ = KeymapMixinMeta

class ListModel(gtk.GenericTreeModel):
    def __init__(self, list, ncolumns):
        gtk.GenericTreeModel.__init__(self)
        self.list = list
        self.ncolumns = ncolumns
    
    # XXX make editable and propagate back to slist ? 
    
    def on_get_flags(self):
        return gtk.TREE_MODEL_LIST_ONLY | gtk.TREE_MODEL_ITERS_PERSIST
    def on_get_n_columns(self):
        return self.ncolumns
    def on_get_column_type(self, n):
        return str
    def on_get_iter(self, path):
        if not self.list:
            return None
        return path[0]
    def on_get_path(self, rowref):
        return (rowref,)
    def on_iter_children(self, rowref):
        return on_iter_nth_child(rowref, 0)
    def on_iter_has_child(self, rowref):
        return False
    def on_iter_parent(self, child):
        return None
    
    def on_iter_n_children(self, rowref):
        if rowref:
            return 0
        return len(self.list)

    def on_get_value(self, rowref, column):
        try:
            return self.list[rowref]
        except IndexError:
            return None

    def on_iter_next(self, rowref):
        rowref += 1
        if rowref < 0 or rowref >= len(self.list):
            return None
        return rowref

    def on_iter_nth_child(self, rowref, n):
        if rowref or n < 0 or n >= len(self.list):
            return None
        return n

def ListBox(list, cursor, columns = ["Value"], search = True, owner = None, font = None):
    model = ListModel(list, len(columns))
    def cursor_changed(obj):
        c = obj.get_cursor()[0]
        cursor.set(c)
    def update_list(*args):
        c = obj.get_cursor()[0]
        obj.freeze_child_notify()
        obj.set_model(None)
        obj.set_model(model)
        obj.thaw_child_notify()
        if c is not None:
            if c[0] >= len(list):
                c = (len(list) - 1,)
            obj.set_cursor(c)
    def update_cursor(cursor):
        obj.set_cursor(cursor.get())
    obj = SetFont(_TreeView(), font)
    obj.owner = owner
    obj.connect("cursor-changed", cursor_changed)
    obj.set_enable_search(search)
    list.register(update_list)
    cursor.register(update_cursor)

    for i, title in enumerate(columns):
        cell = gtk.CellRendererText()
        col = gtk.TreeViewColumn(title)
        col.pack_start(cell, True)
        col.add_attribute(cell, 'text', i)
        col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col.set_resizable(True)
        obj.append_column(col)
    
    obj.set_model(model)
    return Scrolled(obj, owner = owner)

class TreeInterface(object):
    def __len__(self):
        pass
    def __getitem__(self, index):
        pass
    def has_children(self):
        pass
    # columns as attributes

class TreeModel(gtk.GenericTreeModel):
    def __init__(self, tree, columns): 
        gtk.GenericTreeModel.__init__(self)
        self.tree = tree
        self.columns = columns
    
    def _get_node_by_path(self, path):
        node = self.tree
        if not path:
            return node
        for p in path:
            #try:
            node = node[p]
            #except IndexError:
            #    return None
        return node
    
    def on_get_n_columns(self):
        return len(self.columns)
    def on_get_column_type(self, index):
        return str
    def on_get_iter(self, path):
        node = self.tree
        if not node.has_children():
            return None
        return path
    def on_get_path(self, path):
        return path
    def on_iter_children(self, path):
        return self.on_iter_nth_child(path, 0)
    def on_iter_parent(self, path):
        return path[:-1]
    def on_iter_has_child(self, path):
        return self._get_node_by_path(path).has_children()
    def on_get_flags(self):
        return gtk.TREE_MODEL_ITERS_PERSIST
    def on_iter_n_children(self, path):
        return len(self._get_node_by_path(path))
    def on_iter_next(self, path):
        parent = path[:-1]
        next = path[-1] + 1
        if next >= self.on_iter_n_children(parent):
            return None
        return parent + (next,)
    def on_iter_nth_child(self, path, n):
        if path is None:
            path = ()
        if n >= self.on_iter_n_children(path):
            return None
        return path + (n,)
    
    def on_get_value(self, path, colindex):
        node = self._get_node_by_path(path)
        attrname = self.columns[colindex]
        return getattr(node, attrname)

def TreeBox(sforest, columns, tcursor, search = False, owner = None, 
        treelines = True, alternating_color = True, font = None):
    def update_sforest(sforest):
        model = TreeModel(sforest.get(), attrcolumns)
        obj.set_model(None)
        obj.set_model(model)
    def cursor_changed(obj):
        tcursor.set(obj.get_cursor()[0])
    def update_cursor(cursor):
        obj.set_cursor(cursor.get())
    
    obj = SetFont(_TreeView(), font)
    obj.owner = owner
    obj.connect("cursor-changed", cursor_changed)
    obj.set_enable_search(search)
    obj.set_enable_tree_lines(treelines)
    obj.set_rules_hint(alternating_color)
    
    attrcolumns = [attrname for _, attrname in columns]
    for i, (title, _) in enumerate(columns):
        cell = gtk.CellRendererText()
        col = gtk.TreeViewColumn(title)
        col.pack_start(cell, True)
        col.add_attribute(cell, 'text', i)
        col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col.set_resizable(True)
        obj.append_column(col)
    
    tcursor._init(obj)
    tcursor.register(update_cursor)
    sforest.register(update_sforest)
    update_sforest(sforest)
    
    return Scrolled(obj, owner = owner)






















